﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.IO;
using System.Xml.Serialization;
using System.Drawing;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Windows.Media;
using System.Collections;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
    private const int MAX_OLD_MESSAGES = 50;
    private const string PROFILE_DATA_EXT = ".wpd";
    private const string PROFILE_OPTIONS_EXT = ".wpo";
    private const string PROFILE_IMAGE_EXT = ".img";
    private const string LOG_DIR = "..\\WGM_Data\\SystemData\\Logs";
    private const string USER_MESSAGES_DIR = "..\\WGM_Data\\Messages";
    private const string USER_PROFILE_DIR = "..\\WGM_Data\\Users\\profiles";
    private const string USER_OPTIONS_DIR = "..\\WGM_Data\\Users\\info";
    private const string ONLINE_USERS_DIR = "..\\WGM_Data\\OnlineUsers";
    private const string SYSTEM_DIR = "..\\WGM_Data\\SystemData";
    public Service () 
    {

        //Uncomment the following line if using designed components 
        //InitializeComponent(); 
    }

    public UserProfile GetUserData(string userName)
    {
        string fileName = USER_PROFILE_DIR + "\\" + userName + PROFILE_DATA_EXT;
        if (File.Exists(fileName))
        {
            XmlSerializer xs = new XmlSerializer(typeof(UserProfile));
            FileStream fs = File.OpenRead(fileName);
            UserProfile user = xs.Deserialize(fs) as UserProfile;
            fs.Close();
            return user;

        }
        return null;
    }
    [WebMethod]
    public double GetCurrentVersion()
    {


        return 0;
    }
    [WebMethod]
    public bool CreateUser(string userName, string password, string secureQ, string secureA, out string message)
    {
        message = "Server is locked for upgrades... please try again later";
        return false;
        userName = userName.ToLower();
        if (userName == "main")
        {
            message = "The username " + userName + " is reserved for the system use";
            return false;
        }
        try
        {
            if (!Directory.Exists(LOG_DIR))
                Directory.CreateDirectory(LOG_DIR);
            CheckOnlineUsers(userName, password, out message);
            if (!Directory.Exists(USER_PROFILE_DIR))
            {
                Directory.CreateDirectory(USER_PROFILE_DIR);
            }

            if (!Directory.Exists(ONLINE_USERS_DIR))
                Directory.CreateDirectory(ONLINE_USERS_DIR);
            if (!Directory.Exists(USER_OPTIONS_DIR))
                Directory.CreateDirectory(USER_OPTIONS_DIR);
            string[] allUsers = Directory.GetFiles(USER_PROFILE_DIR);
            foreach (string user in allUsers)
            {
                if ((user).ToLower().Trim().EndsWith((userName + PROFILE_DATA_EXT).ToLower().Trim()))
                {
                    message = "User " + userName + " already exists...";
                    return false;
                }
            }
            string filepath = USER_PROFILE_DIR + "\\" + userName + PROFILE_DATA_EXT;
            UserProfile newProfile = new UserProfile(userName, WEncryption.WCryptor.Encrypt(password), secureQ, WEncryption.WCryptor.Encrypt(secureA));
            SaveObject(newProfile, filepath);
            message = "User " + userName + " successfully created!";
            LogMessage(message);
            string userOptionsPath = USER_OPTIONS_DIR + "\\" + userName + PROFILE_OPTIONS_EXT;
            if (!File.Exists(userOptionsPath))
            {
                UserOptions newUserOpt = new UserOptions(userName, userName, "New User", "Arial", 12, 0, "");
                SaveObject(newUserOpt, userOptionsPath);
                LogMessage("options created " + userOptionsPath);
            }
            WGMMessage newUser = new WGMMessage(userName, "", null, MessageTypes.NEW_USER_CREATED, DateTime.Now);
            DepositMessageToAllOnline(newUser, userName, password, out message);
            AddUserToOnlineList(userName);

            return true;
        }
        catch (Exception ex)
        {
            LogError(ex);
            message = "system error";
            return false;
        }
    }
    [WebMethod]
    public bool LogOff(string userName, string requesterUsername, string password, string incomingMessage, out string statusMessage)
    {
        userName = userName.ToLower();
        LogMessage("Log off request by " + requesterUsername + " to log off " + userName);
        if (requesterUsername != userName && requesterUsername != "system")
        {
            if (requesterUsername != "wayne" && requesterUsername != "server")
            {
                statusMessage = "you are not authorized to perform this action";
                return false;
            }
            else
            {
                if (!AuthenticateUser(requesterUsername, password, out statusMessage))
                    return false;
                WGMMessage offlineUser = new WGMMessage(userName, requesterUsername, incomingMessage, MessageTypes.USER_LEAVING, DateTime.Now);

                DepositMessage(userName, offlineUser);
                LogMessage("User " + userName + " being forced to leave by: " + requesterUsername);
                statusMessage = "success";
                return true;
            }
        }
        try
        {
            if (requesterUsername != "system")
            {
                if (!AuthenticateUser(requesterUsername, password, out statusMessage))
                {
                    statusMessage = "user credentials failed";
                    LogMessage("User leaving by own request: " + userName);
                    return false;
                }
            }
            else
            {
                password = WEncryption.WCryptor.Decrypt(GetUserData(userName).Password);
                LogMessage("User leaving by system request: " + userName);
            }
            RemoveOnlineUser(userName);
            WGMMessage offlineUser = new WGMMessage(userName, requesterUsername, userName + "is leaving", MessageTypes.USER_LEAVING, DateTime.Now);
            DepositMessageToAllOnline(offlineUser, userName, password, out statusMessage);
            LogMessage("User left: " + userName);
            statusMessage = "success";
            return true;
        }
        catch (Exception ex)
        {
            LogMessage("There was a failure to log tell online users about " + userName + " leaving\r\n");
            LogError(ex);

            statusMessage = "system error:\r\n" + ex.ToString();
            return false;
        }



    }
    private void RemoveOnlineUser(string userName)
    {
        if (File.Exists(ONLINE_USERS_DIR + "\\" + userName))
            File.Delete(ONLINE_USERS_DIR + "\\" + userName);
    }
    [WebMethod]
    public List<string> GetOnlineUserNames(string userName, string password, out string statusMessage)
    {

        try
        {
            if (!AuthenticateUser(userName, password, out statusMessage))
            {
                return null;
            }
            List<string> retVal = new List<string>();
            if (Directory.Exists(ONLINE_USERS_DIR))
            {
                DirectoryInfo di = new DirectoryInfo(ONLINE_USERS_DIR);
                FileInfo[] allFiles = di.GetFiles();
                foreach (FileInfo fi in allFiles)
                {
                    retVal.Add(fi.Name.ToLower());
                }

            }
            return retVal;
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";
            return null;
        }
    }
    [WebMethod]
    public void SendMessage(WGMMessage message, string userName, string password, out string statusMessage)
    {
        if (message.SenderLogon == null || message.SenderLogon == "")
        {
            statusMessage = "invalid user name";
            return;
        }
        if (!CheckUserOnline(message.SenderLogon, password, out statusMessage))
        {
            statusMessage = "offline users cannot send messages";
            return;
        }
        if (!AuthenticateUser(userName, password, out statusMessage))
        {
            return;
        }
        message.TimeStamp = GetServerTime();
        try
        {
            switch (message.MessageType)
            {
                case MessageTypes.GLOBAL_MESSAGE:
                case MessageTypes.USER_CHANGED_INFO:
                case MessageTypes.USER_LEAVING:
                case MessageTypes.USER_LOGON:
                    DepositMessageToAllOnline(message, userName, password, out statusMessage);
                    break;
                case MessageTypes.PRIVATE_MESSAGE:
                    DepositMessage(message.ReceiverLogon, message);
                    DepositMessage(message.SenderLogon, message);
                    break;
            }
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";

        }
    }
    private bool CheckUserOnline(string userName, string password, out string statusMessage)
    {
        if (userName == null || userName.Trim() == "")
        {
            statusMessage = "invalid user name";
            return false;
        }
        userName = userName.ToLower();
        try
        {
            List<string> onlineUsers = GetOnlineUserNames(userName, password, out statusMessage);
            foreach (string onlineUser in onlineUsers)
            {
                if (onlineUser.ToLower() == userName.ToLower())
                    return true;
            }

            return false;
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw ex;
        }
    }
    [WebMethod]
    public bool RetrieveOldMessages(string userName, string password, out List<WGMMessage> messages, out string statusMessage)
    {
        userName = userName.ToLower();
        try
        {
            if (!CheckUserOnline(userName, password, out statusMessage))
            {
                statusMessage = "You are currently offline";
                messages = null;
                return false;
            }
            AddUserToOnlineList(userName);
            CheckOnlineUsers(userName, password, out statusMessage);
            string folderPath = USER_MESSAGES_DIR + "\\" + userName;
            string oldMessagePath = folderPath + "\\old_messages";
            if (Directory.Exists(oldMessagePath))
            {
                messages = new List<WGMMessage>();
                DirectoryInfo di = new DirectoryInfo(oldMessagePath);
                FileInfo[] fileInfos = di.GetFiles();
                string[] messageFiles = Directory.GetFiles(oldMessagePath);
                int max = MAX_OLD_MESSAGES;
                int current = 0;
                foreach (FileInfo messageFile in fileInfos)
                {
                    WGMMessage newMessage = LoadObject(messageFile.FullName, typeof(WGMMessage)) as WGMMessage;

                    messages.Add(newMessage);

                    current++;
                    if (current >= max)
                        break;
                }
                statusMessage = "success";
                return true;
            }
            statusMessage = "no such path: " + folderPath;
            messages = null;
            return false;
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw ex;
        }

    }
    [WebMethod]
    public List<string> GetSystemFontNames()
    {
        ICollection<System.Windows.Media.FontFamily> fonts = Fonts.SystemFontFamilies;
        List<string> retVal = new List<string>();
        foreach (System.Windows.Media.FontFamily ff in fonts)
        {
            retVal.Add(ff.ToString());
        }
        return retVal;
    }

    [WebMethod]
    public bool RetrieveMessages(string userName, string password, string macAddress, out List<WGMMessage> messages, out string statusMessage)
    {
        if (userName == null || userName.Trim() == "")
        {
            statusMessage = "Invalid user name";
            messages = null;
            return false;
        }
        userName = userName.ToLower();
        statusMessage = "ok so far";
        if (!AuthenticateUser(userName, password, out statusMessage))
        {
            messages = null;
            return false;
        }
        try
        {
            if (!CheckUserOnline(userName, password, out statusMessage))
            {
                statusMessage = "You are currently offline";
                messages = null;
                return false;
            }
            AddUserToOnlineList(userName);
            CheckOnlineUsers(userName, password, out statusMessage);
            string folderPath = USER_MESSAGES_DIR + "\\" + userName;
            string oldMessagePath = folderPath + "\\old_messages";
            if (Directory.Exists(folderPath))
            {
                messages = new List<WGMMessage>();
                DirectoryInfo di = new DirectoryInfo(folderPath);
                FileInfo[] fileInfos = di.GetFiles();
                string[] messageFiles = Directory.GetFiles(folderPath);
                int max = 20;
                int current = 0;
                foreach (FileInfo messageFile in fileInfos)
                {

                    WGMMessage newMessage = LoadObject(messageFile.FullName, typeof(WGMMessage)) as WGMMessage;

                    messages.Add(newMessage);
                    messageFile.Delete();
                    //MoveToOldMessages(messageFile, userName);

                    current++;
                    if (current >= max)
                        break;
                }
                statusMessage = "success";
                return true;
            }
            statusMessage = "no such path: " + folderPath;
            messages = null;
            return false;
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = ex.ToString();
            messages = null;
            return false;
        }
        statusMessage = "huh";
        messages = null;
        return false;
    }


    private void CheckOnlineUsers(string userName, string password, out string statusMessage)
    {
        try
        {
            if (Directory.Exists(ONLINE_USERS_DIR))
            {
                DirectoryInfo di = new DirectoryInfo(ONLINE_USERS_DIR);
                FileInfo[] onlineUsers = di.GetFiles();
                foreach (FileInfo fi in onlineUsers)
                {
                    DateTime creation = fi.LastWriteTime;
                    DateTime now = DateTime.Now;
                    TimeSpan elapsed = now - creation;
                    if (elapsed.TotalSeconds > 30)
                    {
                        LogOff(fi.Name, "system", password, null, out statusMessage);
                        RemoveOnlineUser(fi.Name);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "failure, check logs";
            return;

        }
        statusMessage = "success";
    }

    private void DepositMessageToAllOnline(WGMMessage message, string userName, string password, out string statusMessage)
    {
        try
        {
            List<string> onlineUsers = GetOnlineUserNames(userName, password, out statusMessage);
            foreach (string onlineUser in onlineUsers)
            {
                DepositMessage(onlineUser, message);
            }
        }
        catch (Exception ex)
        {
            LogError(ex);

        }
        statusMessage = "done";
    }
    private void DepositMessage(string userName, WGMMessage message)
    {
        userName = userName.ToLower();
        try
        {
            if (Directory.Exists(USER_MESSAGES_DIR + "\\" + userName))
            {
                string messagePath = USER_MESSAGES_DIR + "\\" + userName + "\\message-" + DateTime.Now.ToString("MddyyyHHmmssffff");
                SaveObject(message, messagePath);
            }
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw ex;
        }
    }


    [WebMethod]
    public List<string> GetAllUserNames(string userName, string password, out string statusMessage)
    {
        try
        {
            if (!AuthenticateUser(userName, password, out statusMessage))
            {
                return null;
            }
            List<string> retVal = new List<string>();
            if (Directory.Exists(USER_PROFILE_DIR))
            {
                DirectoryInfo di = new DirectoryInfo(USER_PROFILE_DIR);
                FileInfo[] allFiles = di.GetFiles();
                foreach (FileInfo fi in allFiles)
                {
                    retVal.Add(fi.Name.ToLower());
                }

            }
            return retVal;
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";
            return null;
        }
    }
    [WebMethod]
    public UserOptions GetUserOptions(string userName, string requesterUserName, string password, out string statusMessage)
    {
        userName = userName.ToLower();
        try
        {
            if (!AuthenticateUser(requesterUserName, password, out statusMessage))
            {
                return null;
            }
            string fileName = USER_OPTIONS_DIR + "\\" + userName + PROFILE_OPTIONS_EXT;
            if (File.Exists(fileName))
            {
                return (LoadObject(fileName, typeof(UserOptions)) as UserOptions);
            }
            return null;
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";
            return null;
        }
    }
    [WebMethod]
    public List<UserOptions> GetAllUserOptions(string userName, string password, out string statusMessage)
    {
        try
        {
            if (!AuthenticateUser(userName, password, out statusMessage))
            {
                return null;
            }
            List<UserOptions> retVal = new List<UserOptions>();
            if (Directory.Exists(USER_OPTIONS_DIR))
            {
                string[] userFiles = Directory.GetFiles(USER_OPTIONS_DIR);
                foreach (string userFile in userFiles)
                {
                    if (userFile.EndsWith(PROFILE_OPTIONS_EXT))
                    {
                        UserOptions options = LoadObject(userFile, typeof(UserOptions)) as UserOptions;
                        retVal.Add(options);
                    }
                }
            }
            return retVal;
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";
            return null;
        }
    }
    [WebMethod]
    public DateTime GetServerTime()
    {
        return DateTime.Now;
    }
    [WebMethod]
    public bool UserLogin(string userName, string password, string macAddress, out string message)
    {
        //if (userName != "wayne" && userName != "bigdub")
        //{
        //    message = "Server is locked for upgrades... please try again later";
        //    return false;
        //}
        if (userName == null || userName.Trim() == "")
        {
            message = "invalid user name";
            return false;
        }
        userName = userName.ToLower();
        try
        {
            CheckOnlineUsers(userName, password, out message);
            if (AuthenticateUser(userName, password, out message))
            {
                string userDirPath = USER_MESSAGES_DIR + "\\" + userName;

                if (!Directory.Exists(userDirPath))
                {
                    Directory.CreateDirectory(userDirPath);
                }

                string oldMessagePath = userDirPath + "\\old_messages";
                if (!Directory.Exists(oldMessagePath))
                    Directory.CreateDirectory(oldMessagePath);
                if (!Directory.Exists(ONLINE_USERS_DIR))
                    Directory.CreateDirectory(ONLINE_USERS_DIR);
                if (!Directory.Exists(USER_OPTIONS_DIR))
                    Directory.CreateDirectory(USER_OPTIONS_DIR);

                WGMMessage newUser = new WGMMessage(userName, "", null, MessageTypes.USER_LOGON, DateTime.Now);
                DepositMessageToAllOnline(newUser, userName, password, out message);
                AddUserToOnlineList(userName);
                LogMessage("User logging in: " + userName + " - " + macAddress);
                return true;
            }
            else
            {
                LogMessage("Failed user login: " + userName + " - " + macAddress);
                return false;
            }
        }
        catch (Exception ex)
        {
            LogError(ex);

        }
        message = "system error";
        return false;
    }
    private void LogMessage(string message)
    {
        if (!Directory.Exists(LOG_DIR))
            Directory.CreateDirectory(LOG_DIR);
        string fileName = LOG_DIR + "\\messagelog.log";


        StreamWriter sw = File.AppendText(fileName);
        sw.WriteLine(DateTime.Now.ToString() + " - " + message);
        sw.Close();
    }
    private void LogError(Exception ex)
    {

        if (!Directory.Exists(LOG_DIR))
            Directory.CreateDirectory(LOG_DIR);

        string fileName = LOG_DIR + "\\errlog.log";

        StreamWriter sw = File.AppendText(fileName);
        sw.WriteLine(DateTime.Now.ToString() + " - " + ex.ToString());
        sw.Close();

    }
    [WebMethod]
    public byte[] GetUserImage(string userName, string requesterUserName, string password, out string statusMessage)
    {
        userName = userName.ToLower();
        try
        {
            if (!AuthenticateUser(requesterUserName, password, out statusMessage))
            {
                return null;
            }
            string imagePath = USER_OPTIONS_DIR + "\\" + userName + PROFILE_IMAGE_EXT;
            if (File.Exists(imagePath))
            {

                Stream stream = new FileStream(imagePath, System.IO.FileMode.Open, FileAccess.Read, FileShare.Delete);
                IFormatter formatter = new BinaryFormatter();
                byte[] retVal = (formatter.Deserialize(stream)) as byte[];
                stream.Close();
                return retVal;
            }
            return null;
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";
            return null;
        }

    }
    [WebMethod]
    public void SetUserImage(string userName, string password, out string statusMessage, byte[] image)
    {
        if (userName == null || userName.Trim() == "")
        {
            statusMessage = "invalid user name";
            return;
        }

        userName = userName.ToLower();
        try
        {
            if (!AuthenticateUser(userName, password, out statusMessage))
            {
                return;
            }
            string imagePath = USER_OPTIONS_DIR + "\\" + userName + PROFILE_IMAGE_EXT;
            if (image == null)
            {
                File.Delete(imagePath);
                return;
            }

            Stream stream = new FileStream(imagePath, System.IO.FileMode.Create, FileAccess.Write, FileShare.Delete);
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, image);
            stream.Close();
            LogMessage("Setting new image for user: " + userName);
            WGMMessage newMessage = new WGMMessage(userName, "", null, MessageTypes.USER_CHANGED_IMAGE, DateTime.Now);
            DepositMessageToAllOnline(newMessage, userName, password, out statusMessage);
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";
        }
    }
    [WebMethod]
    public void SetUserOptions(string username, string password, out string statusMessage, UserOptions options)
    {
        if (username == null || username.Trim() == "")
        {
            statusMessage = "invalid user name";
            return;
        }
        if (options.UserName == null || options.UserName.Trim() == "")
        {
            statusMessage = "invalid options";
            return;
        }
        username = username.ToLower();
        try
        {
            if (!AuthenticateUser(username, password, out statusMessage))
            {
                return;
            }
            string filePath = USER_OPTIONS_DIR + "\\" + username + PROFILE_OPTIONS_EXT;
            SaveObject(options, filePath);
            LogMessage("Setting new options for user: " + username + "\r\n" + options.DisplayName + "\r\n" + options.Email + "\r\n" + options.FontColour + "\r\n" + options.FontSize + "\r\n" + options.FontStyle + "\r\n" + options.FontStyleInt + "\r\n" + options.UserFontFace + "\r\n" + options.UserStatus);
            WGMMessage newMessage = new WGMMessage(username, "", null, MessageTypes.USER_CHANGED_INFO, DateTime.Now);
            DepositMessageToAllOnline(newMessage, username, password, out statusMessage);
        }
        catch (Exception ex)
        {
            LogError(ex);
            statusMessage = "system error";

        }
    }
    private void AddUserToOnlineList(string userName)
    {
        userName = userName.ToLower();
        try
        {
            string onlineFilePath = ONLINE_USERS_DIR + "\\" + userName;
            FileStream fs = File.Create(onlineFilePath);
            fs.Close();
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw ex;
        }

    }
    private void SaveObject(object obj, string path)
    {
        try
        {
            XmlSerializer ser = new XmlSerializer(obj.GetType());
            FileStream fs = File.Create(path);
            ser.Serialize(fs, obj);
            fs.Close();
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw ex;
        }
    }
    private object LoadObject(string path, Type type)
    {
        try
        {
            XmlSerializer xs = new XmlSerializer(type);
            FileStream fs = File.OpenRead(path);

            object retVal = xs.Deserialize(fs);
            fs.Close();
            return retVal;
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw ex;
        }
    }
    private bool AuthenticateUser(string username, string password, out string message)
    {
        username = username.ToLower();
        try
        {
            string fileName = USER_PROFILE_DIR + "\\" + username + PROFILE_DATA_EXT;
            if (File.Exists(fileName))
            {
                XmlSerializer xs = new XmlSerializer(typeof(UserProfile));
                FileStream fs = File.OpenRead(fileName);
                UserProfile user = xs.Deserialize(fs) as UserProfile;
                fs.Close();
                if (WEncryption.WCryptor.Decrypt(user.Password) == password)
                {
                    message = "Success";
                    return true;
                }
                else
                {
                    message = "Incorrect password";
                    return false;
                }
            }
            else
            {
                message = "No such user: " + username + "\r\nOr the server is down for repairs";
                return false;
            }
        }
        catch (Exception ex)
        {
            LogError(ex);
            message = "system error";
            return false;
        }
    }


}
